package pl.enigmatic.util;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Vector;


/**
 * This <code>class</code> holds utility java reflection connected methods. It assumes some typical java code style convensions such as the following:
 * <ul>
 * <li>fields in classes always start lowercase</li>
 * <li>setter for all fields follow the simple rule: <code>myValue</code> &rarr; <code>setMyValue</code></li>
 * <li>getter for non-<code>boolean</code> fields follow the the simple rule: <code>myValue</code> &rarr; <code>getMyValue</code></li>
 * <li>getter for <code>boolean</code> fields follow the the simple rule: <code>myBooleanValue</code> &rarr; <code>isMyBooleanValue</code></li>
 * </ul>
 * It also assumes that these types are <b>primitives</b>:
 * <ul>
 * <li><code>byte</code>, {@link Byte}</li>
 * <li><code>short</code>, {@link Short}</li>
 * <li><code>int</code>, {@link Integer}</li>
 * <li><code>long</code>, {@link Long}</li>
 * <li><code>float</code>, {@link Float}</li>
 * <li><code>double</code>, {@link Double}</li>
 * <li><code>char</code>, {@link Character}</li>
 * <li><code>boolean</code>, {@link Boolean}</li>
 * </ul>
 * The left-hand side classes are <b>strict primitives</b>. And the list of <b>extended primitives</b> contains additionally:
 * <ul>
 * <li>{@link String}</li>
 * </ul>
 * @author reggie_7
 */
public class Reflection {

	/** This protected constructor prevents <code>class</code> from being instantiated. */
	protected Reflection() {}

	/** a map holding pairs (primitive type) &rarr; (its wrapper <code>class</code>) */
	private final static HashMap<Class<?>, Class<?>> wrappers = new HashMap<Class<?>, Class<?>>();
	private final static HashMap<Class<?>, Class<?>> primitives = new HashMap<Class<?>, Class<?>>();
	static {
		wrappers.put(Byte.TYPE, Byte.class);
		wrappers.put(Short.TYPE, Short.class);
		wrappers.put(Integer.TYPE, Integer.class);
		wrappers.put(Long.TYPE, Long.class);
		wrappers.put(Float.TYPE, Float.class);
		wrappers.put(Double.TYPE, Double.class);
		wrappers.put(Character.TYPE, Character.class);
		wrappers.put(Boolean.TYPE, Boolean.class);
		for (final Entry<Class<?>, Class<?>> e : wrappers.entrySet()) {
			primitives.put(e.getValue(), e.getKey());
		}
	}

	/** setter prefix */
	public static final String SET = "set";
	/** getter prefix for non-<code>boolean</code> fields */
	public static final String GET = "get";
	/** getter prefix for <code>boolean</code> fields */
	public static final String IS = "is";
	public static final String ADD = "add";
	public static final String PUT = "put";

	/**
	 * Gets the setter name for a given field name.
	 * @param field field name
	 * @return setter name
	 */
	public static final String setter(final String field) {
		return SET + Strings.firstUpper(field);
	}

	/**
	 * Gets the setter name for a given field.
	 * @param field the field
	 * @return setter name
	 */
	public static final String setter(final Field field) {
		return setter(field.getName());
	}

	/**
	 * Gets the getter name for a given field name.
	 * @param field field name
	 * @param isBoolean indicates whether this field is of type <code>boolean</code>, or its wrapper type
	 * @return getter name
	 */
	private static final String getter(final String field, final boolean isBoolean) {
		return (isBoolean ? IS : GET) + Strings.firstUpper(field);
	}

	/**
	 * Gets the getter name for a given field.
	 * @param field the field
	 * @return getter name
	 */
	public static final String getter(final Field field) {
		return getter(field.getName(), field.getType().equals(Boolean.TYPE) || field.getType().equals(Boolean.class));
	}

	/**
	 * Gets the getter name for a given field in the given <code>class</code>. If there is none - it returns <code>null</code>.
	 * @param clazz the <code>class</code> to investigate
	 * @param field the field
	 * @return getter name
	 */
	public static final String getter(final Class<?> clazz, final String field) {
		Field f = null;
		try {
			f = clazz.getDeclaredField(field);
		} catch (final Exception e) {}
		if (f != null) {
			return getter(f);
		} else {
			Method m;
			try {
				m = clazz.getMethod(GET + Strings.firstUpper(field), new Class<?>[] {});
			} catch (final Exception e) {
				try {
					m = clazz.getMethod(IS + Strings.firstUpper(field), new Class<?>[] {});
				} catch (final Exception ex) {
					m = null;
				}
			}
			if (m != null) { return m.getName(); }
			return null;
		}
	}

	public static final String getOperationFieldName(final String operation, final String name) {
		if (name.startsWith(operation) && name.length() > operation.length()) { return Strings.firstLower(name.substring(operation.length())); }
		return null;
	}

	/**
	 * Tries to get the name of a field connected with the passed setter name. The setter must obey the simple rules which can be found
	 * {@link Reflection here}. If it does - the returned value is a valid field name, otherwise - <code>null</code>.
	 * @param setter setter method name
	 * @return the connected field name, or <code>null</code>
	 */
	public static final String getSetterFieldName(final Method setter) {
		return getOperationFieldName(SET, setter.getName());
	}

	public static final String getGetterFieldName(final Method setter) {
		return getOperationFieldName(GET, setter.getName());
	}

	public static final Class<?> primitive(final Class<?> clazz) {
		if (primitives.containsKey(clazz)) { return primitives.get(clazz); }
		return clazz;
	}

	/**
	 * Finds the wraper <code>class</code> for the given parameter. It is the java wrapper <code>class</code> for a primitive type if
	 * <code>clazz</code> is one, or the <code>clazz</code> itself otherwise.
	 * @param clazz the <code>class</code> type to get a wrapper <code>class</code> type for
	 * @return the wrapper <code>class</code> type
	 */
	public static final Class<?> wrapper(final Class<?> clazz) {
		if (clazz.isPrimitive()) { return wrappers.get(clazz); }
		return clazz;
	}

	/**
	 * Finds the depth of a given array <code>class</code> type, or returns 0, if <code>clazz</code> does not represent an array type.
	 * @param clazz the array <code>class</code> type to investigate
	 * @return the depth of given array <code>class</code>, or 0 if <code>clazz</code> is not an array
	 */
	public static final int arrayDepth(final Class<?> clazz) {
		Class<?> c = clazz;
		int depth = -1;
		while (c != null) {
			c = c.getComponentType();
			depth++;
		}
		return depth;
	}

	/**
	 * Finds the base type of a given array <code>class</code> type, or returns the <code>clazz</code> itself, if it does not represent an array type.
	 * @param clazz the array <code>class</code> type to investigate
	 * @return the base <code>class</code> type of given array <code>class</code>
	 */
	public static final Class<?> arrayBase(final Class<?> clazz) {
		Class<?> c = clazz;
		if (clazz != null) {
			while (c.isArray()) {
				c = c.getComponentType();
			}
		}
		return c;
	}

	/**
	 * Constructs an array <code>class</code> type for a given base <code>class</code> and <code>depth</code> value.
	 * @param clazz base <code>class</code> type for returned array <code>class</code> type
	 * @param depth returned array <code>class</code> type's depth
	 * @return an array <code>class</code> type for the given parameters
	 */
	public static final Class<?> arrayType(final Class<?> clazz, final int depth) {
		return depth > 0 ? Array.newInstance(clazz, new int[depth]).getClass() : clazz;
	}

	/**
	 * Gets all methods declared in the given <code>class</code>, which have a signature of a setter. It does not check whether the connected field
	 * exists or not.
	 * @param clazz the <code>class</code> to find setters from
	 * @return setters for the given <code>class</code> parameter
	 * @see Reflection#getDeclaredFieldSetters(Class)
	 */
	public static final HashMap<String, Method> getDeclaredSetters(final Class<?> clazz) {
		final HashMap<String, Method> setters = new HashMap<String, Method>();
		for (final Method setter : clazz.getDeclaredMethods()) {
			final int mod = setter.getModifiers();
			final String name = getSetterFieldName(setter);
			if (name != null && setter.getParameterTypes().length == 1 && Modifier.isPublic(mod) && !Modifier.isStatic(mod)) {
				setters.put(name, setter);
			}
		}
		return setters;
	}

	/**
	 * Gets all methods declared in the given <code>class</code>, which have a signature of a getter. It does not check whether the connected field
	 * exists or not.
	 * @param clazz the <code>class</code> to find setters from
	 * @return setters for the given <code>class</code> parameter
	 * @see Reflection#getDeclaredFieldSetters(Class)
	 */
	public static final HashMap<String, Method> getDeclaredGetters(final Class<?> clazz) {
		final HashMap<String, Method> getters = new HashMap<String, Method>();
		for (final Method getter : clazz.getDeclaredMethods()) {
			final int mod = getter.getModifiers();
			final String name = getGetterFieldName(getter);
			if (name != null && getter.getParameterTypes().length == 0 && Modifier.isPublic(mod) && !Modifier.isStatic(mod)
					&& !isVoid(getter.getReturnType())) {
				getters.put(name, getter);
			}
		}
		return getters;
	}

	// TODO komentarz
	public static final Method getDeclaredMethod(final Class<?> clazz, final String name, final Class<?>... parameters) {
		try {
			return clazz.getDeclaredMethod(name, parameters);
		} catch (final Exception e) {}
		return null;
	}

	public static final Method getMethod(final Class<?> clazz, final String name, final Class<?>... parameters) {
		try {
			return clazz.getMethod(name, parameters);
		} catch (final Exception e) {}
		return null;
	}

	/**
	 * Gets all methods declared in the given <code>class</code>, which have a signature of a setter and are backed by proper fields in the given
	 * class.
	 * @param clazz the <code>class</code> to find setters from
	 * @return setters backed by proper fields for the given <code>class</code> parameter
	 * @see Reflection#getDeclaredSetters(Class)
	 */
	public static final HashMap<String, Method> getDeclaredFieldSetters(final Class<?> clazz) {
		final HashMap<String, Method> setters = new HashMap<String, Method>();
		for (final Entry<String, Method> entry : getDeclaredSetters(clazz).entrySet()) {
			try {
				final String name = entry.getKey();
				final Method setter = entry.getValue();
				final Field field = clazz.getDeclaredField(name);
				if (field != null && field.getType().equals(setter.getParameterTypes()[0]) && !Modifier.isStatic(field.getModifiers())) {
					setters.put(name, setter);
				}
			} catch (final Exception e) {}
		}
		return setters;
	}

	/**
	 * Tries to instanciate the given <code>class</code> by calling the default zero-parameter constructor. If it fails to create an instance - it
	 * returns <code>null</code>.
	 * @param <X> the type of returned <code>class</code> instance
	 * @param clazz the <code>class</code> to instanciate
	 * @return <code>new</code> instance of the given <code>class</code>
	 */
	public static final <X> X newInstance(final Class<X> clazz) {
		try {
			return clazz.newInstance();
		} catch (final Exception e) {
		}
		return null;
	}

	/**
	 * Does the same as {@link Reflection#newInstance(Class)} or returns <code>null</code>, if the <code>class</code> does not exist.
	 * @param clazz the <code>class</code> name
	 */
	@SuppressWarnings("unchecked")
	public static final <X> X newInstance(final String clazz) {
		try {
			return (X) newInstance(classForName(clazz));
		} catch (final Exception e) {}
		return null;
	}

	/**
	 * Finds all non-<code>static</code> fields in the given <code>class</code>.
	 * @param clazz the <code>class</code> to investigate
	 * @return all non-<code>static</code> fields in the <code>class</code> given
	 */
	public static final HashMap<String, Field> getDeclaredFields(final Class<?> clazz) {
		final HashMap<String, Field> res = new HashMap<String, Field>();
		for (final Field f : clazz.getDeclaredFields()) {
			if (!Modifier.isStatic(f.getModifiers())) {
				res.put(f.getName(), f);
			}
		}
		return res;
	}

	/**
	 * Investigates whether the given class is strict primitive in the sense defined by {@link Reflection}.
	 */
	public static final boolean isStrictPrimitive(final Class<?> clazz) {
		return wrappers.containsKey(clazz);
	}

	/**
	 * Investigates whether the given class is primitive in the sense defined by {@link Reflection}.
	 */
	public static final boolean isPrimitive(final Class<?> clazz) {
		return isStrictPrimitive(clazz) || primitives.containsKey(clazz);
	}

	/**
	 * Investigates whether the given class is extended primitive in the sense defined by {@link Reflection}.
	 */
	public static final boolean isExtendedPrimitive(final Class<?> clazz) {
		return isPrimitive(clazz) || String.class.equals(clazz);
	}

	/**
	 * Checks whether the given <code>class</code> represents a collection.
	 * @param clazz the <code>class</code> to investigate
	 * @return <code>true</code>, if the <code>class</code> implements the {@link Collection} <code>interface</code>
	 */
	public static final boolean isCollection(final Class<?> clazz) {
		return Collection.class.isAssignableFrom(clazz);
	}

	/**
	 * Only calls {@link Reflection#isCollection(Class)} for the field's type.
	 * @param field the field to investigate
	 */
	public static final boolean isCollection(final Field field) {
		return isCollection(field.getType());
	}

	/**
	 * Checks whether the given <code>class</code> represents a map.
	 * @param clazz the <code>class</code> to investigate
	 * @return <code>true</code>, if the <code>class</code> implements the {@link Map} <code>interface</code>
	 */
	public static final boolean isMap(final Class<?> clazz) {
		return Map.class.isAssignableFrom(clazz);
	}

	/**
	 * Only calls {@link Reflection#isMap(Class)} for the field's type.
	 * @param field the field to investigate
	 */
	public static final boolean isMap(final Field field) {
		return isMap(field.getType());
	}

	/**
	 * Gets the <code>class</code> with the given <code>name</code> or returns <code>null</code>, if there is none.
	 * @param name the desired <code>class</code> name
	 * @return the <code>class</code> for the given <code>name</code>
	 */
	public static final Class<?> classForName(final String name) {
		Class<?> c = null;
		try {
			c = Class.forName(name);
		} catch (final Exception e) {
			final Iterator<Class<?>> i = wrappers.keySet().iterator();
			while (c == null && i.hasNext()) {
				final Class<?> cls = i.next();
				if (cls.getName().equals(name)) {
					c = cls;
				}
			}
		}
		return c;
	}

	/**
	 * @param <X> the <code>class</code> type parameter
	 * @param clazz the <code>class</code> to find a constructor for
	 * @param parameterTypes parameter types for the desired constructor
	 * @return the constructor found by {@link Class#getConstructor(Class...)}, or <code>null</code>, if not found
	 */
	public static final <X> Constructor<X> getConstructor(final Class<X> clazz, final Class<?>... parameterTypes) {
		try {
			return clazz.getConstructor(parameterTypes);
		} catch (final Exception e) {
			return null;
		}
	}

	public static boolean setFieldValue(final Object object, final String fieldName, final Object value) {
		Class<?> c = object.getClass();
		Field field = null;
		while (field == null && c != null) {
			try {
				field = c.getDeclaredField(fieldName);
			} catch (final Exception e) {
				field = null;
			}
			c = c.getSuperclass();
		}
		if (field != null) {
			final boolean a = field.isAccessible();
			field.setAccessible(true);
			try {
				field.set(object, value);
				field.setAccessible(a);
				return true;
			} catch (final Exception e) {} finally {
				field.setAccessible(a);
			}
		}
		return false;
	}

	public static Field getDeclaredFieldRecursively(final Class<?> clazz, final String name) {
		Field f = null;
		Class<?> c = clazz;
		while (f == null && c != null) {
			try {
				f = c.getDeclaredField(name);
			} catch (final Exception e) {
				f = null;
			}
			c = c.getSuperclass();
		}
		return f;
	}

	public static boolean isVoid(final Class<?> cls) {
		return Void.TYPE.equals(cls) || Void.class.equals(cls);
	}

	public static boolean classExists(final String clazz) {
		try {
			Class.forName(clazz, false, Reflection.class.getClassLoader());
			return true;
		} catch (final Exception e) {}
		return false;
	}

	public static List<String> getDirectSubpackages(final String packageName) {
		try {
			final ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
			assert classLoader != null;
			final String path = packageName.replace('.', '/');
			Enumeration<URL> resources;
			resources = classLoader.getResources(path);
			final List<File> dirs = new ArrayList<File>();
			while (resources.hasMoreElements()) {
				final URL resource = resources.nextElement();
				dirs.add(new File(resource.getFile()));
			}
			final ArrayList<String> packages = new ArrayList<String>();
			for (final File dir : dirs) {
				if (dir.isDirectory()) {
					for (final File sub : dir.listFiles()) {
						final String name = packageName + "." + sub.getName();
						if (sub.isDirectory() && !packages.contains(name)) {
							packages.add(name);
						}
					}
				}
			}
			return packages;
		} catch (final IOException e) {
			return null;
		}
	}

	public static List<String> getAllSubpackages(final String packageName) {
		final List<String> res = new Vector<String>();
		final List<String> mine = getDirectSubpackages(packageName);
		res.addAll(mine);
		for (final String sub : mine) {
			res.addAll(getAllSubpackages(sub));
		}
		return res;
	}

}
